package org.melanesia.beans;

import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.security.AccessController;
import java.security.PrivilegedAction;
import java.util.HashMap;
import java.util.Locale;
import java.util.Map;

class BeanUtils {
	
    private static final Map<Class<?>, Class<?>> WRAPPERS = new HashMap<Class<?>, Class<?>>();

    static {
        WRAPPERS.put(byte.class,    Byte.class);
        WRAPPERS.put(short.class,   Short.class);
        WRAPPERS.put(char.class,    Character.class);
        WRAPPERS.put(int.class,     Integer.class);
        WRAPPERS.put(long.class,    Long.class);
        WRAPPERS.put(float.class,   Float.class);
        WRAPPERS.put(double.class,  Double.class);
        WRAPPERS.put(boolean.class, Boolean.class);
    }
    
    private static final Map<Class<?>, Object> DEFAULT_PRIMITIVE_VALUES = new HashMap<Class<?>, Object>();
    
    static {
    	DEFAULT_PRIMITIVE_VALUES.put(byte.class,    0);
    	DEFAULT_PRIMITIVE_VALUES.put(short.class,   0);
    	DEFAULT_PRIMITIVE_VALUES.put(char.class,    0);
    	DEFAULT_PRIMITIVE_VALUES.put(int.class,     0);
    	DEFAULT_PRIMITIVE_VALUES.put(long.class,    0);
    	DEFAULT_PRIMITIVE_VALUES.put(float.class,   0f);
    	DEFAULT_PRIMITIVE_VALUES.put(double.class,  0d);
    	DEFAULT_PRIMITIVE_VALUES.put(boolean.class, false);
    }
    
	static final boolean isSimpleProperty(String propertyName) {
        return !propertyName.contains(".");
    }
    
	static final String getSimpleProperty(String compositePropertyName) {
        return compositePropertyName.substring(0, compositePropertyName.indexOf("."));
    }
    
	static final String getSubProperty(String compositePropertyName) {
        return compositePropertyName.substring(getSimpleProperty(compositePropertyName).length() + 1);
    }
	
	static final String sqlToCamelCase(String str) {
		if (str == null) return null;
		if ("".equals(str)) return str;
		if (str.length() == 1) return str.toLowerCase(Locale.getDefault());
		
		
		String[] tokens = str.split("_");
        StringBuilder b = new StringBuilder();
        for(String t : tokens) {
            if(t.length() == 0) continue;
            b.append(t.substring(0,1).toUpperCase());
            b.append(t.substring(1).toLowerCase());
        }
        
        String ret = b.toString();
        
        return ret.substring(0, 1).toLowerCase(Locale.getDefault()) + ret.substring(1);
	}
	
	public static void main(String[] t) {
		
		String[] strings = new String[] {
				"X",
				"ID",
				"ID_NAD",
				"ID_NAD_ALL_OPE",
				"test_TEST_test",
				"a.b.b.d"
		};
		
		for (String str : strings) {
			System.out.println(str + " => " + "set" + capitalize(sqlToCamelCase(str)));
		}
	}
	
	static final String capitalize(String str) {
		if (str == null) return null;
		if ("".equals(str)) return str;
		if (str.length() == 1) return str.toUpperCase(Locale.getDefault());
		
		return str.substring(0, 1).toUpperCase(Locale.getDefault()) + str.substring(1);
	}
	
	static final void setAccessible(final Method method, final boolean accessible) {
        AccessController.doPrivileged(new PrivilegedAction<Void>() {
            @Override
            public Void run() {
                method.setAccessible(accessible);
                return null;
            }
        });
    }
	
	static final void setAccessible(final Field field, final boolean accessible) {
        AccessController.doPrivileged(new PrivilegedAction<Void>() {
            @Override
            public Void run() {
                field.setAccessible(accessible);
                return null;
            }
        });
    }
    
    static final Class<?> getWrapperClass(Class<?> primitiveClass) {
        return WRAPPERS.get(primitiveClass);
    }
    
    static final Class<?> getPrimitiveClass(Class<?> wrapperClass) {
        for (Map.Entry<Class<?>, Class<?>> entry : WRAPPERS.entrySet()) {
            if (entry.getValue().equals(wrapperClass)) {
                return entry.getKey();
            }
        }
        
        return null;
    }
    
    static final boolean isAssignableFrom(Class<?> class1, Class<?> class2) {

        // First, check if class1 is assignable from class2
        if (class1.isAssignableFrom(class2)) {
            return true;
        } else {
            
            // If not, there is still posibility, that we have an autoboxing issue, 
            // where for example int.class is not assignable from Integer.class and vice versa.
            for (Map.Entry<Class<?>, Class<?>> entry : WRAPPERS.entrySet()) {
                if ((entry.getKey().isAssignableFrom(class1) && entry.getValue().isAssignableFrom(class2)) ||
                    (entry.getKey().isAssignableFrom(class2) && entry.getValue().isAssignableFrom(class1))) {
                    return true;
                }
            }
        }
        
        return false;
    }

    /**
     * Smart autoboxing-aware cast. If value is of primitive or wrapper type of the given class, 
     * then just returns value without actual casting. This way lets JVM handle auto(un)boxing.
     * 
     * @param value
     * @param toClass
     * @return
     */
    static Object cast(Object value, Class<?> toClass) {
    	if (value == null) {
    		if (isPrimitiveClass(toClass)) {
    			return DEFAULT_PRIMITIVE_VALUES.get(toClass);
    		} else {
    			return null;
    		}
    	} else {
    		if (isWrapper(toClass, value.getClass()) || isPrimitive(toClass, value.getClass())) {
    			return value;
    		} else {
    			return toClass.cast(value);
    		}
    	}
    }
    
    static boolean isWrapperClass(Class<?> clazz) {
    	return WRAPPERS.containsValue(clazz);
    }
    
    static boolean isPrimitiveClass(Class<?> clazz) {
    	return WRAPPERS.containsKey(clazz);
    }
    
    static boolean isPrimitive(Class<?> class1, Class<?> class2) {
    	// class1 is a primitive type of class2 then and only then, 
    	// when class2 is a wrapper of class1
    	return isWrapper(class2, class1);
    }
    
    /**
     * Determines if class1 is a wrapper class, for primitive type specified by class2
     * @param class1
     * @param class2
     * @return
     */
    static boolean isWrapper(Class<?> class1, Class<?> class2) {
    	return class1.equals(WRAPPERS.get(class2));
    }
}
